package nes;

import java.awt.Point;
import java.util.List;
import robocode.Bullet;

/**
 * This class keeps track of opponents status.
 * 
 * @author Sergey Negrashov
 */
public class Opponent {

  /**
   * Name of the opponent.
   */
  private String name;

  /**
   * Size of the circular buffers.
   */
  public static final int MAX_POSITION = 6;

  /**
   * Last recorded energy of opponents.
   */
  private CircularBuffer<Double> energy;

  /**
   * When the energies were recorded.
   */
  private CircularBuffer<Long> energyTime;

  /**
   * Buffer for x positions.
   */
  private CircularBuffer<Double> positionX;

  /**
   * Buffer for y positions.
   */
  private CircularBuffer<Double> positionY;

  /**
   * Buffer for position update times.
   */
  private CircularBuffer<Double> positionTime;

  /**
   * Constructor for the opponent.
   * 
   * @param opponentName name of out opponent.
   */
  public Opponent(String opponentName) {
    this.name = opponentName;
    energy = new CircularBuffer<Double>(MAX_POSITION);
    positionX = new CircularBuffer<Double>(MAX_POSITION);
    positionY = new CircularBuffer<Double>(MAX_POSITION);
    energyTime = new CircularBuffer<Long>(MAX_POSITION);
    positionTime = new CircularBuffer<Double>(MAX_POSITION);
  }

  /**
   * Returns last seen energy of the opponent.
   * 
   * @return last seen energy.
   */
  public double getLastEnergy() {
    return energy.getTop();
  }

  /**
   * Returns last seen position of the opponent.
   * 
   * @return last seen position.
   */
  public Point getLastPosition() {
    Point ret = new Point();
    ret.setLocation(positionX.getTop(), positionY.getTop());
    return ret;
  }

  /**
   * Update the status of the opponent, and check if the opponent fired.
   * 
   * @param opponentName name to be checked with the stored name.
   * @param energy observed energy.
   * @param pos point where the robot was last seen.
   * @param time when the update occured.
   * @return if the robot fired since last hit.
   */
  public boolean checkUpdate(String opponentName, double energy, Point pos, long time) {
    if (!opponentName.equals(this.name)) {
      return false;
    }
    boolean ret = false;
    if (!this.energy.isEmpty() && this.energy.getTop() != null && this.energy.getTop() > energy) {
      ret = true;
    }
    this.energy.add(energy);
    this.positionX.add(pos.getX());
    this.positionY.add(pos.getY());
    this.energyTime.add(time);
    this.positionTime.add(time + 0.0);
    return ret;
  }

  /**
   * Call this method when our robot was hit. Computes the new energy of the opponent robot.
   * 
   * @param bulletPower power of the bullet.
   * @param time when the event occurred.
   */
  public void didDamage(double bulletPower, long time) {

    this.energy.add(energy.getTop() + bulletPower * 3);
    this.energyTime.add(time);
  }

  /**
   * Call this method when our opponent was hit. Computes the new energy of the opponent robot.
   * 
   * @param b bullet which hit the opponent.
   * @param time when the event occurred.
   */
  public void tookDamage(Bullet b, long time) {
    double loss = b.getPower() * 4;
    if (b.getPower() > 1) {
      loss += 2 * b.getPower() - 1;
    }
    energy.add(energy.getTop() - loss);
    positionX.add(b.getX());
    positionY.add(b.getY());
    positionTime.add(time + 0.0);
    energyTime.add(time);
  }

  /**
   * Returns the name of the opponent.
   * 
   * @return name of the opponent.
   */
  public String getName() {
    return name;
  }

  /**
   * Returns the {@code List} of X positions.
   * 
   * @return list of positions.
   */
  public List<Double> getXPositions() {
    return positionX.toList();
  }

  /**
   * Returns the {@code List} of X positions.
   * 
   * @return list of positions.
   */
  public List<Double> getYPositions() {
    return positionY.toList();
  }

  /**
   * Returns the future X coordinate for the opponent.
   * 
   * @param time which turn to calculate for.
   * @return computed x position at given time.
   */
  public double getFutureX(long time) {
    if (positionX.size() < 2) {
      return positionX.getTop();
    }
    Double[] x = positionX.toArray(new Double[positionX.size()]);
    Double[] t = positionTime.toArray(new Double[positionTime.size()]);
    double ret;
    ret = getFutureCoordiate(t, x, time);
    return ret;
  }

  /**
   * Returns the future Y coordinate for the opponent.
   * 
   * @param time which turn to calculate for.
   * @return computed y position at given time.
   */
  public double getFutureY(long time) {
    if (positionY.size() < 2) {
      return positionY.getTop();
    }
    Double[] y = positionY.toArray(new Double[positionY.size()]);
    Double[] t = positionTime.toArray(new Double[positionTime.size()]);
    double ret;
    ret = getFutureCoordiate(t, y, time);
    return ret;
  }

  /**
   * Returns the future position for the opponent.
   * 
   * @param time which turn to calculate for.
   * @return computed position at given time.
   */
  public Point getFuturePoint(long time) {
    Point ret = new Point();
    ret.setLocation(getFutureX(time), getFutureY(time));
    return ret;
  }

  /**
   * Computes the future coordinates using linear regression at a given x point.
   * (Kenney and Keeping 1962).
   * @param x Array of x coordinates.
   * @param y Array of y coordinates.
   * @param xp x coordinate to calculate y for.
   * @return y coordinate to calculate for x.
   */
    private static double getFutureCoordiate(Double[] x, Double[] y, double xp) {
    double xBar = 0;
    double yBar = 0;
    double xxBar = 0.0;
    double xyBar = 0.0;
    double a;
    double b;
    int n = x.length;
    for (int i = 0; i < n; i++) {
      xBar += x[i];
      yBar += y[i];
    }
    xBar /= n;
    yBar /= n;
    
    for (int i = 0; i < n; i++) {
      xxBar += (x[i] - xBar) * (x[i] - xBar);
      xyBar += (x[i] - xBar) * (y[i] - yBar);
    }
    a = xyBar / xxBar;
    b = yBar - a * xBar;

    return a * xp + b;
  }
}
